package com.googlecode.kipler.container.dl;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import com.googlecode.kipler.container.dl.creational.IndividualFactory;
import com.googlecode.kipler.container.dl.operational.ABoxChangeObserver;
import com.googlecode.kipler.container.dl.operational.ABoxChangeSubject;
import com.googlecode.kipler.container.dl.operational.ConcreteABoxChangeSubject;
import com.googlecode.kipler.container.dl.operational.IndividualIterator;
import com.googlecode.kipler.container.dl.operational.ObjectRoleAssertionIterator;
import com.googlecode.kipler.container.graph.RelationalModel;
import com.googlecode.kipler.container.graph.RelationalNode;
import com.googlecode.kipler.satisfiability.reasoner.UnsatisfiableException;
import com.googlecode.kipler.syntax.Name;
import com.googlecode.kipler.syntax.NameManager;
import com.googlecode.kipler.syntax.Role;
import com.googlecode.kipler.syntax.concept.Concept;
import com.googlecode.kipler.syntax.formula.ConceptAssertion;
import com.googlecode.kipler.syntax.formula.Formula;
import com.googlecode.kipler.syntax.formula.ObjectRoleAssertion;
import com.googlecode.kipler.syntax.formula.SameIndividualAssertion;

/**
 * 
 * @author İnanç Seylan
 * 
 */
public class GenericABox implements ABox, ABoxChangeSubject {
	private final static Role INEQUALITY = new Role("!=");

	private final static Role EQUALITY = new Role("==");

	private boolean hasClash;

	private RelationalModel<Individual, Role, Name> roleG = new RelationalModel<Individual, Role, Name>();

	private RelationalModel<Individual, Role, Name> sameIndvG = new RelationalModel<Individual, Role, Name>();

	private World world;

	private ConcreteABoxChangeSubject formulaAddSubject = new ConcreteABoxChangeSubject();

	private IndividualFactory indvCreator;

	private ABox representedABox;

	public GenericABox(IndividualFactory indvCreator) {
		this.indvCreator = indvCreator;
		setRepresentedABox(this);
	}

	public GenericABox(IndividualFactory indvCreator, ABox representedABox) {
		this.indvCreator = indvCreator;
		setRepresentedABox(representedABox);
	}

	public World getWorld() {
		return world;
	}

	public void setWorld(World world) {
		this.world = world;
	}

	private RelationalNode<Individual, Role, Name> safeGetNode(
			RelationalModel<Individual, Role, Name> model, String indvStr) {
		Name indvName = NameManager.getInstance().getName(indvStr);
		RelationalNode<Individual, Role, Name> result;
		if (model.contains(indvName)) {
			result = model.getNode(indvName);
		} else {
			Individual newIndividual = indvCreator.createIndividual();
			newIndividual.setName(indvStr);
			result = new RelationalNode<Individual, Role, Name>(newIndividual);
			model.add(result);
		}
		return result;
	}

	public void add(Formula formula) throws UnsatisfiableException {
		if (formula instanceof ConceptAssertion) {
			add((ConceptAssertion) formula);
		} else if (formula instanceof ObjectRoleAssertion) {
			add((ObjectRoleAssertion) formula);
		} else if (formula instanceof SameIndividualAssertion) {
			add((SameIndividualAssertion) formula);
		}
	}

	public void add(ConceptAssertion ca) throws UnsatisfiableException {
		RelationalNode<Individual, Role, Name> node = safeGetNode(roleG, ca
				.getName());
		node.getValue().add(ca.getConcept());
		formulaAddSubject.notifyAddition(getRepresentedABox(), ca);
	}

	public void add(ObjectRoleAssertion ra) throws UnsatisfiableException {
		RelationalNode<Individual, Role, Name> owner = safeGetNode(roleG, ra
				.getOwner());
		RelationalNode<Individual, Role, Name> filler = safeGetNode(roleG, ra
				.getFiller());
		owner.addOutgoing(ra.getRole(), filler);
		formulaAddSubject.notifyAddition(getRepresentedABox(), ra);
	}

	public void add(SameIndividualAssertion assertion)
			throws UnsatisfiableException {
		if (!assertion.getLhs().equals(assertion.getRhs())) {
			// create the individuals in the role graph if they don't already
			// exist
			safeGetNode(roleG, assertion.getLhs());
			safeGetNode(roleG, assertion.getRhs());
			// create the individuals in the inequality graph
			RelationalNode<Individual, Role, Name> lhs = safeGetNode(sameIndvG,
					assertion.getLhs());
			RelationalNode<Individual, Role, Name> rhs = safeGetNode(sameIndvG,
					assertion.getRhs());
			Role rel = assertion.isNegated() ? INEQUALITY : EQUALITY;
			lhs.addOutgoing(rel, rhs);
			// must be symmetric
			rhs.addOutgoing(rel, lhs);
			formulaAddSubject.notifyAddition(getRepresentedABox(), assertion);
		}
	}

	public boolean contains(Formula formula) {
		boolean result = false;
		if (formula instanceof ConceptAssertion) {
			result = contains((ConceptAssertion) formula);
		} else if (formula instanceof ObjectRoleAssertion) {
			result = contains((ObjectRoleAssertion) formula);
		} else if (formula instanceof SameIndividualAssertion) {
			result = contains((SameIndividualAssertion) formula);
		}
		return result;
	}

	public boolean contains(SameIndividualAssertion ia) {
		Name lhs = NameManager.getInstance().getName(ia.getLhs());
		Name rhs = NameManager.getInstance().getName(ia.getRhs());
		boolean nodesExist = sameIndvG.contains(lhs) && sameIndvG.contains(rhs);
		Role rel = ia.isNegated() ? INEQUALITY : EQUALITY;
		return nodesExist
				&& sameIndvG.getNode(lhs).containsOutgoing(rel,
						sameIndvG.getNode(rhs));
	}

	public boolean contains(ConceptAssertion ca) {
		return contains(ca.getName())
				&& getIndividual(ca.getName()).contains(ca.getConcept());
	}

	public boolean contains(ObjectRoleAssertion ra) {
		boolean result = false;
		Name ownerName = NameManager.getInstance().getName(ra.getOwner());
		Name fillerName = NameManager.getInstance().getName(ra.getFiller());
		if (contains(ra.getOwner()) && contains(ra.getFiller())) {
			RBox rbox = getWorld().getRBox();
			RelationalNode<Individual, Role, Name> ownerNode = roleG
					.getNode(ownerName);
			RelationalNode<Individual, Role, Name> fillerNode = roleG
					.getNode(fillerName);
			for (Role role : rbox.getSubRoles(ra.getRole())) {
				if (ownerNode.containsOutgoing(role, fillerNode)
						|| fillerNode.containsOutgoing(role.copy()
								.toggleInverse(), ownerNode)) {
					result = true;
					break;
				}
			}
		}
		return result;
	}

	public boolean contains(String individual) {
		return roleG.contains(NameManager.getInstance().getName(individual));
	}

	public boolean containsEdge(Role role, Individual from, Individual to) {
		return roleG.contains(from.getKey())
				&& roleG.contains(to.getKey())
				&& roleG.getNode(from.getKey()).containsOutgoing(role,
						roleG.getNode(to.getKey()));
	}

	public boolean containsEdge(Individual node, Individual succCandidate) {
		return roleG.contains(succCandidate.getKey())
				&& roleG.contains(node.getKey())
				&& roleG.getNode(node.getKey()).containsOutgoing(
						roleG.getNode(succCandidate.getKey()));
	}

	public void remove(String name) {
		Name key = NameManager.getInstance().getName(name);
		// roleG also contains the individuals in sameIndvG
		if (roleG.contains(key)) {
			RelationalNode<Individual, Role, Name> node = roleG.getNode(key);
			formulaAddSubject.notifyRemoval(getRepresentedABox(), node
					.getValue());
			roleG.remove(node);
		}
		if (sameIndvG.contains(key)) {
			sameIndvG.remove(sameIndvG.getNode(key));
		}
	}

	public Iterator<Individual> predecessorIterator(Individual filler) {
		IndividualIterator result;
		if (roleG.contains(filler.getKey())) {
			Iterator<RelationalNode<Individual, Role, Name>> iter = roleG
					.getNode(filler.getKey()).getIncoming().iterator();
			result = new IndividualIterator(iter);
		} else {
			result = new IndividualIterator();
		}
		return result;
	}

	public Iterator<Individual> successorIterator(Individual owner) {
		IndividualIterator result;
		if (roleG.contains(owner.getKey())) {
			Iterator<RelationalNode<Individual, Role, Name>> iter = roleG
					.getNode(owner.getKey()).getOutgoing().iterator();
			result = new IndividualIterator(iter);
		} else {
			result = new IndividualIterator();
		}
		return result;
	}

	public Iterator<Individual> neighborIterator(Individual indv) {
		IndividualIterator result;
		if (roleG.contains(indv.getKey())) {
			result = new IndividualIterator();
			result.add(roleG.getNode(indv.getKey()).getOutgoing().iterator());
			result.add(roleG.getNode(indv.getKey()).getIncoming().iterator());
		} else {
			result = new IndividualIterator();
		}
		return result;
	}

	public Iterator<ObjectRoleAssertion> predecessorIterator(Role role,
			Individual filler) {
		ObjectRoleAssertionIterator result = new ObjectRoleAssertionIterator(
				filler);
		if (roleG.contains(filler.getKey())) {
			RBox rbox = getWorld().getRBox();
			for (Role subRole : rbox.getSubRoles(role)) {
				Iterator<RelationalNode<Individual, Role, Name>> iter = roleG
						.getNode(filler.getKey()).getIncoming(subRole)
						.iterator();
				result.addPredecessorIterator(subRole, iter);
			}
		}
		return result;
	}

	public Iterator<ObjectRoleAssertion> successorIterator(Role role,
			Individual owner) {
		ObjectRoleAssertionIterator result = new ObjectRoleAssertionIterator(
				owner);
		if (roleG.contains(owner.getKey())) {
			RBox rbox = getWorld().getRBox();
			for (Role subRole : rbox.getSubRoles(role)) {
				Iterator<RelationalNode<Individual, Role, Name>> iter = roleG
						.getNode(owner.getKey()).getOutgoing(subRole)
						.iterator();
				result.addSuccessorIterator(subRole, iter);
			}
		}
		return result;
	}

	public Iterator<ObjectRoleAssertion> neighborIterator(Role role,
			Individual owner) {
		ObjectRoleAssertionIterator result = new ObjectRoleAssertionIterator(
				owner);
		if (roleG.contains(owner.getKey())) {
			RBox rbox = getWorld().getRBox();
			for (Role subRole : rbox.getSubRoles(role)) {
				result.addSuccessorIterator(subRole, roleG.getNode(
						owner.getKey()).getOutgoing(subRole).iterator());
				Role invRole = subRole.copy().toggleInverse();
				result.addPredecessorIterator(invRole, roleG.getNode(
						owner.getKey()).getIncoming(invRole).iterator());
			}
		}
		return result;
	}

	public Set<Role> getEdges(Individual owner, Individual filler) {
		return roleG.getNode(owner.getKey()).edgesOutgoing(
				roleG.getNode(filler.getKey()));
	}

	public Iterator<Individual> differentIndividualIterator(
			Individual individual) {
		IndividualIterator result;
		if (sameIndvG.contains(individual.getKey())) {
			Iterator<RelationalNode<Individual, Role, Name>> iter = sameIndvG
					.getNode(individual.getKey()).getOutgoing(INEQUALITY)
					.iterator();
			result = new IndividualIterator(iter);
		} else {
			result = new IndividualIterator();
		}
		return result;
	}

	public Iterator<Individual> sameIndividualIterator(Individual individual) {
		IndividualIterator result;
		if (sameIndvG.contains(individual.getKey())) {
			Iterator<RelationalNode<Individual, Role, Name>> iter = sameIndvG
					.getNode(individual.getKey()).getOutgoing(EQUALITY)
					.iterator();
			result = new IndividualIterator(iter);
		} else {
			result = new IndividualIterator();
		}
		return result;
	}

	public Individual getIndividual(String name) {
		Name indvName = NameManager.getInstance().getName(name);
		Individual result = null;
		if (roleG.contains(indvName)) {
			result = roleG.getNode(indvName).getValue();
		}
		return result;
	}

	public Iterator<Individual> individualIterator() {
		return new IndividualIterator(roleG.getNodes().iterator());
	}

	public Set<Formula> getFormulas() {
		HashSet<Formula> result = new HashSet<Formula>();
		for (RelationalNode<Individual, Role, Name> node : roleG.getNodes()) {
			for (Iterator<Concept> i = node.getValue().conceptIterator(); i
					.hasNext();) {
				Concept concept = i.next();
				result.add(new ConceptAssertion(concept, node.getValue()
						.getName().toString()));
			}
			for (RelationalNode<Individual, Role, Name> toNode : node
					.getOutgoing()) {
				for (Role edge : node.edgesOutgoing(toNode)) {
					result.add(new ObjectRoleAssertion(edge, node.getValue()
							.getName(), toNode.getValue().getName()));
				}
			}
		}
		for (RelationalNode<Individual, Role, Name> node : sameIndvG.getNodes()) {
			for (RelationalNode<Individual, Role, Name> toNode : node
					.getOutgoing()) {
				for (Role edge : node.edgesOutgoing(toNode)) {
					SameIndividualAssertion ia = new SameIndividualAssertion(
							node.getValue().getName(), toNode.getValue()
									.getName());
					if (edge.equals(INEQUALITY)) {
						ia.toggleNegated();
					}
					result.add(ia);
				}
			}
		}
		return result;
	}

	public boolean isSatisfiable() {
		return !hasClash;
	}

	public GenericABox copy() {
		GenericABox abox = new GenericABox(indvCreator);
		abox.hasClash = hasClash;
		abox.roleG = roleG.copy();
		abox.sameIndvG = sameIndvG.copy();
		abox.formulaAddSubject = formulaAddSubject;
		return abox;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj != null && (obj.getClass().equals(this.getClass()))) {
			GenericABox other = (GenericABox) obj;
			return hasClash == other.hasClash && roleG.equals(other.roleG)
					&& sameIndvG.equals(other.sameIndvG);
		}
		return false;
	}

	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("ABox");
		buffer.append(" = {");
		for (Formula f : getFormulas())
			buffer.append(f + ", ");
		buffer.delete(buffer.length() - 2, buffer.length());
		buffer.append("}");
		return buffer.toString();
	}

	public void setHasClash(boolean hasClash) {
		this.hasClash = hasClash;
	}

	public void addObserver(ABoxChangeObserver observer) {
		formulaAddSubject.addObserver(observer);
	}

	public ABox getRepresentedABox() {
		return representedABox;
	}

	public void setRepresentedABox(ABox representedABox) {
		this.representedABox = representedABox;
	}

	@Override
	public int size() {
		return roleG.size();
	}

}
